<?php


namespace App\Service;

use EchoPHP\Cache\CacheManager;
use Exception;

class Captcha {

    private $cache;

    public function __construct(CacheManager $cache) {
        $this->cache = $cache;
    }

    /**
     * 生成验证码
     * @param $type
     * @param $uuid
     * @return array 生成的验证码结果
     * @throws \Exception
     */
    public function generate($type, $uuid) {
        list($key, $code, $expire, $intervalTime) = $this->generateCode($type, $uuid);
        if ($intervalTime > 0) {
            $waitTime = $this->overIntervalTime($key, $intervalTime);
            if ($waitTime > 0) {
                return ['error' => -1, 'message' => '操作过于频繁，请稍后再试', 'waittime' => $waitTime];
            }
        }
        $storeContent = json_encode([
            'code' => $code,
            'datetime' => time(),
            'try_times' => 0,
            'pre_try_times' => 0,
        ]);
        $this->cache->connect()->set($key, $storeContent, 86400);
        return ['error' => 0, 'code' => $code, 'expire' => $expire];
    }

    /**
     * 校验验证码
     * @param $type 验证码类型
     * @param $uuid 客户标识
     * @param $checkCode    待校验的校验码
     * @param bool $preCheck 是否预校验，如图形验证码可以加入预校验机制，方便用户及时知道输入是否正确
     * @return bool
     * @throws \Exception
     */
    public function check($type, $uuid, $checkCode, $preCheck = false) {
        $captchaConf = config('captcha.' . strtolower($type));
        if (is_null($captchaConf)) {
            throw new Exception('verifycode type is not define');
        }

        $key = $this->getStoredKeyName($captchaConf['prefix'], $uuid);
        $storeContent = $this->cache->connect()->get($key, '');
        if (empty($storeContent)) {
            return false;
        }
        $storeContent = json_decode($storeContent, true);

        /**
         * 校验逻辑
         * 验证码已过期，则直接删除
         * 校验失败，记录失败次数，判断是否需要删除缓存中的验证码
         * 校验成功，判断如果是预校验，返回成功，如果是正常校验，返回成功并删除缓存中存放的验证码
         */
        if ($storeContent['datetime'] + $captchaConf['expire'] + 30 < time()) {
            $this->cache->connect()->delete($key);
            return false;
        }
        $strict = isset($captchaConf['strict']) && $captchaConf['strict'] ? true : false;
        if (!$strict) {
            $storeCheckCode = strtolower($storeContent['code']);
            $checkCode = strtolower($checkCode);
        } else {
            $storeCheckCode = $storeContent['code'];
        }
        if ($storeCheckCode == $checkCode) {
            if ($preCheck) {
                return true;
            }
            $this->cache->connect()->delete($key);
            return true;
        }

        $allowTryTimes = $preCheck ? ($captchaConf['pre_try_times'] ?? 0) : ($captchaConf['try_times'] ?? 1);
        $tryTimes = $preCheck ? $storeContent['pre_try_times'] : $storeContent['try_times'];
        $tryTimes += 1;
        if ($tryTimes >= $allowTryTimes) {
            $this->cache->connect()->delete($key);
            return false;
        }
        if ($preCheck) {
            $storeContent['pre_try_times'] += 1;
        } else {
            $storeContent['try_times'] += 1;
        }
        $storeContent = json_encode($storeContent);
        $this->cache->connect()->set($key, $storeContent, 86400);
        return false;
    }

    /**
     * 生成验证码
     * @param $type
     * @param $uuid
     * @return array
     * @throws \Exception
     */
    private function generateCode($type, $uuid) {
        $captchaConf = config('captcha.' . strtolower($type));
        if (is_null($captchaConf)) {
            throw new Exception('verifycode type is not define');
        }
        $source = $captchaConf['source'];
        $sourceLen = strlen($source);
        $code = '';
        for ($i = 0; $i < $captchaConf['length']; $i++) {
            $code .= $source[rand(0, $sourceLen - 1)];
        }

        $key = $this->getStoredKeyName($captchaConf['prefix'], $uuid);
        $intervalTime = isset($captchaConf['interval_time']) ? intval($captchaConf['interval_time']) : 0;
        return [$key, $code, $captchaConf['expire'], $intervalTime];
    }

    /**
     * 检查超过验证码发送间隔时间
     * @param $key
     * @param $intervalTime
     * @return int 剩余发送时间间隔
     */
    private function overIntervalTime($key, $intervalTime) {
        $storeContent = $this->cache->connect()->get($key, '');
        if (empty($storeContent)) {
            return 0;
        }
        $storeContent = json_decode($storeContent, true);
        $lifeTime = time() - $storeContent['datetime'];
        if ($lifeTime < $intervalTime) {
            return $intervalTime - $lifeTime;
        }
        return 0;
    }

    /**
     * 生成存储用键值
     * @param $prefix
     * @param $uuid
     * @return string
     */
    private function getStoredKeyName($prefix, $uuid) {
        return 'cache_verify:' . trim($prefix, '_') . ':' . str_replace('.', '-', $uuid);
    }

}